/**
  @file

  @brief
  Windows NT Service class library.

  Copyright Abandoned 1998 Irena Pancirov - Irnet Snc
  This file is public domain and comes with NO WARRANTY of any kind
*/
#include <windows.h>
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
#include "nt_servc.h"


static NTService *pService;

/* ------------------------------------------------------------------------

 -------------------------------------------------------------------------- */
NTService::NTService()
{
    bOsNT	     = FALSE;
    //service variables
    ServiceName      = NULL;
    hExitEvent	     = 0;
    bPause	     = FALSE;
    bRunning	     = FALSE;
    hThreadHandle    = 0;
    fpServiceThread  = NULL;
    //time-out variables
    nStartTimeOut    = 15000;
    nStopTimeOut     = 86400000;
    nPauseTimeOut    = 5000;
    nResumeTimeOut   = 5000;
    //install variables
    dwDesiredAccess  = SERVICE_ALL_ACCESS;
    dwServiceType    = SERVICE_WIN32_OWN_PROCESS;
    dwStartType      = SERVICE_AUTO_START;
    dwErrorControl   = SERVICE_ERROR_NORMAL;
    szLoadOrderGroup = NULL;
    lpdwTagID	     = NULL;
    szDependencies   = NULL;
    my_argc	     = 0;
    my_argv	     = NULL;
    hShutdownEvent   = 0;
    nError	     = 0;
    dwState	     = 0;
}

/* ------------------------------------------------------------------------

 -------------------------------------------------------------------------- */
NTService::~NTService()
{
    if (ServiceName != NULL)
        delete[] ServiceName;
}
/* ------------------------------------------------------------------------

 -------------------------------------------------------------------------- */

BOOL NTService::GetOS()
{
    bOsNT = FALSE;
    memset(&osVer, 0, sizeof(OSVERSIONINFO));
    osVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

    if (GetVersionEx(&osVer)) {
        if (osVer.dwPlatformId == VER_PLATFORM_WIN32_NT)
            bOsNT = TRUE;
    }

    return bOsNT;
}


/**
  Registers the main service thread with the service manager.

  @param ServiceThread  pointer to the main programs entry function
                        when the service is started
*/


long NTService::Init(LPCSTR szInternName, void *ServiceThread)
{
    pService = this;
    fpServiceThread = (THREAD_FC)ServiceThread;
    ServiceName = new char[lstrlen(szInternName) + 1];
    lstrcpy(ServiceName, szInternName);
    SERVICE_TABLE_ENTRY stb[] = {
        { (char *)szInternName, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
        { NULL, NULL }
    };
    return StartServiceCtrlDispatcher(stb); //register with the Service Manager
}


/**
  Installs the service with Service manager.

  nError values:
  - 0  success
  - 1  Can't open the Service manager
  - 2  Failed to create service.
*/


BOOL NTService::Install(int startType, LPCSTR szInternName,
                        LPCSTR szDisplayName,
                        LPCSTR szFullPath, LPCSTR szAccountName,
                        LPCSTR szPassword)
{
    BOOL ret_val = FALSE;
    SC_HANDLE newService, scm;

    if (!SeekStatus(szInternName, 1))
        return FALSE;

    char szFilePath[_MAX_PATH];
    GetModuleFileName(NULL, szFilePath, sizeof(szFilePath));

    // open a connection to the SCM
    if (!(scm = OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE)))
        printf("Failed to install the service (Couldn't open the SCM)\n");
    else {			// Install the new service
        if (!(newService =
                    CreateService(scm,
                                  szInternName,
                                  szDisplayName,
                                  dwDesiredAccess,//default: SERVICE_ALL_ACCESS
                                  dwServiceType,	//default: SERVICE_WIN32_OWN_PROCESS
                                  //default: SERVICE_AUTOSTART
                                  (startType == 1 ? SERVICE_AUTO_START :
                                   SERVICE_DEMAND_START),
                                  dwErrorControl,	//default: SERVICE_ERROR_NORMAL
                                  szFullPath,	//exec full path
                                  szLoadOrderGroup, //default: NULL
                                  lpdwTagID,	//default: NULL
                                  szDependencies,	//default: NULL
                                  szAccountName,	//default: NULL
                                  szPassword)))	//default: NULL
            printf("Failed to install the service (Couldn't create service)\n");
        else {
            printf("Service successfully installed.\n");
            CloseServiceHandle(newService);
            ret_val = TRUE;				// Everything went ok
        }

        CloseServiceHandle(scm);
    }

    return ret_val;
}


/**
  Removes  the service.

  nError values:
  - 0  success
  - 1  Can't open the Service manager
  - 2  Failed to locate service
  - 3  Failed to delete service.
*/


BOOL NTService::Remove(LPCSTR szInternName)
{
    BOOL ret_value = FALSE;
    SC_HANDLE service, scm;

    if (!SeekStatus(szInternName, 0))
        return FALSE;

    nError = 0;

    // open a connection to the SCM
    if (!(scm = OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE)))
        printf("Failed to remove the service (Couldn't open the SCM)\n");

    else {
        if ((service = OpenService(scm, szInternName, DELETE))) {
            if (!DeleteService(service))
                printf("Failed to remove the service\n");
            else {
                printf("Service successfully removed.\n");
                ret_value = TRUE;				// everything went ok
            }

            CloseServiceHandle(service);

        } else
            printf("Failed to remove the service (Couldn't open the service)\n");

        CloseServiceHandle(scm);
    }

    return ret_value;
}

/**
  this function should be called before the app. exits to stop
  the service
*/
void NTService::Stop(void)
{
    SetStatus(SERVICE_STOP_PENDING, NO_ERROR, 0, 1, 60000);
    StopService();
    SetStatus(SERVICE_STOPPED, NO_ERROR, 0, 1, 1000);
}

/**
  This is the function that is called from the
  service manager to start the service.
*/


void NTService::ServiceMain(DWORD argc, LPTSTR *argv)
{
    // registration function
    if (!(pService->hServiceStatusHandle =
                RegisterServiceCtrlHandler(pService->ServiceName,
                                           (LPHANDLER_FUNCTION)
                                           NTService::ServiceCtrlHandler)))
        goto error;

    // notify SCM of progress
    if (!pService->SetStatus(SERVICE_START_PENDING, NO_ERROR, 0, 1, 8000))
        goto error;

    // create the exit event
    if (!(pService->hExitEvent = CreateEvent (0, TRUE, FALSE, 0)))
        goto error;

    if (!pService->SetStatus(SERVICE_START_PENDING, NO_ERROR, 0, 3,
                             pService->nStartTimeOut))
        goto error;

    // save start arguments
    pService->my_argc = argc;
    pService->my_argv = argv;

    // start the service
    if (!pService->StartService())
        goto error;

    // wait for exit event
    WaitForSingleObject (pService->hExitEvent, INFINITE);

    // wait for thread to exit
    if (WaitForSingleObject (pService->hThreadHandle, INFINITE) == WAIT_TIMEOUT)
        CloseHandle(pService->hThreadHandle);

    pService->Exit(0);
    return;
error:
    pService->Exit(GetLastError());
    return;
}



void NTService::SetRunning()
{
    if (pService)
        pService->SetStatus(SERVICE_RUNNING, NO_ERROR, 0, 0, 0);
}

void NTService::SetSlowStarting(unsigned long timeout)
{
    if (pService)
        pService->SetStatus(SERVICE_START_PENDING, NO_ERROR, 0, 0, timeout);
}


/* ------------------------------------------------------------------------
   StartService() - starts the application thread
 -------------------------------------------------------------------------- */

BOOL NTService::StartService()
{
    // Start the real service's thread (application)
    if (!(hThreadHandle = (HANDLE) _beginthread((THREAD_FC)fpServiceThread, 0,
                          (void *) this)))
        return FALSE;

    bRunning = TRUE;
    return TRUE;
}
/* ------------------------------------------------------------------------

 -------------------------------------------------------------------------- */
void NTService::StopService()
{
    bRunning = FALSE;

    // Set the event for application
    if (hShutdownEvent)
        SetEvent(hShutdownEvent);

    // Set the event for ServiceMain
    SetEvent(hExitEvent);
}
/* ------------------------------------------------------------------------

 -------------------------------------------------------------------------- */
void NTService::PauseService()
{
    bPause = TRUE;
    SuspendThread(hThreadHandle);
}
/* ------------------------------------------------------------------------

 -------------------------------------------------------------------------- */
void NTService::ResumeService()
{
    bPause = FALSE;
    ResumeThread(hThreadHandle);
}
/* ------------------------------------------------------------------------

 -------------------------------------------------------------------------- */
BOOL NTService::SetStatus (DWORD dwCurrentState, DWORD dwWin32ExitCode,
                           DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint,
                           DWORD dwWaitHint)
{
    BOOL bRet;
    SERVICE_STATUS serviceStatus;
    dwState = dwCurrentState;
    serviceStatus.dwServiceType	= SERVICE_WIN32_OWN_PROCESS;
    serviceStatus.dwCurrentState = dwCurrentState;

    if (dwCurrentState == SERVICE_START_PENDING)
        serviceStatus.dwControlsAccepted = 0;	//don't accept control events
    else
        serviceStatus.dwControlsAccepted =    (SERVICE_ACCEPT_STOP |
                                               SERVICE_ACCEPT_PAUSE_CONTINUE |
                                               SERVICE_ACCEPT_SHUTDOWN);

    // if a specific exit code is defined,set up the win32 exit code properly
    if (dwServiceSpecificExitCode == 0)
        serviceStatus.dwWin32ExitCode = dwWin32ExitCode;
    else
        serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;

    serviceStatus.dwServiceSpecificExitCode = dwServiceSpecificExitCode;
    serviceStatus.dwCheckPoint = dwCheckPoint;
    serviceStatus.dwWaitHint   = dwWaitHint;

    // Pass the status to the Service Manager
    if (!(bRet = SetServiceStatus (hServiceStatusHandle, &serviceStatus)))
        StopService();

    return bRet;
}
/* ------------------------------------------------------------------------

 -------------------------------------------------------------------------- */
void NTService::ServiceCtrlHandler(DWORD ctrlCode)
{
    DWORD  dwState;

    if (!pService)
        return;

    dwState = pService->dwState; // get current state

    switch(ctrlCode) {
    case SERVICE_CONTROL_SHUTDOWN:
    case SERVICE_CONTROL_STOP:
        dwState = SERVICE_STOP_PENDING;
        pService->SetStatus(SERVICE_STOP_PENDING, NO_ERROR, 0, 1,
                            pService->nStopTimeOut);
        pService->StopService();
        break;

    default:
        pService->SetStatus(dwState, NO_ERROR, 0, 0, 0);
        break;
    }

    //pService->SetStatus(dwState, NO_ERROR,0, 0, 0);
}

/* ------------------------------------------------------------------------

 -------------------------------------------------------------------------- */

void NTService::Exit(DWORD error)
{
    if (hExitEvent)
        CloseHandle(hExitEvent);

    // Send a message to the scm to tell that we stop
    if (hServiceStatusHandle)
        SetStatus(SERVICE_STOPPED, error, 0, 0, 0);

    // If the thread has started kill it ???
    // if (hThreadHandle) CloseHandle(hThreadHandle);
}

/* ------------------------------------------------------------------------

 -------------------------------------------------------------------------- */

BOOL NTService::SeekStatus(LPCSTR szInternName, int OperationType)
{
    BOOL ret_value = FALSE;
    SC_HANDLE service, scm;

    // open a connection to the SCM
    if (!(scm = OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE))) {
        DWORD ret_error = GetLastError();

        if (ret_error == ERROR_ACCESS_DENIED) {
            printf("Install/Remove of the Service Denied!\n");

            if (!is_super_user())
                printf("That operation should be made by an user with Administrator privileges!\n");

        } else
            printf("There is a problem for to open the Service Control Manager!\n");

    } else {
        if (OperationType == 1) {
            /* an install operation */
            if ((service = OpenService(scm, szInternName, SERVICE_ALL_ACCESS ))) {
                LPQUERY_SERVICE_CONFIG ConfigBuf;
                DWORD dwSize;
                ConfigBuf = (LPQUERY_SERVICE_CONFIG) LocalAlloc(LPTR, 4096);
                printf("The service already exists!\n");

                if (QueryServiceConfig(service, ConfigBuf, 4096, &dwSize))
                    printf("The current server installed: %s\n",
                           ConfigBuf->lpBinaryPathName);

                LocalFree(ConfigBuf);
                CloseServiceHandle(service);

            } else
                ret_value = TRUE;

        } else {
            /* a remove operation */
            if (!(service = OpenService(scm, szInternName, SERVICE_ALL_ACCESS )))
                printf("The service doesn't exist!\n");
            else {
                SERVICE_STATUS ss;
                memset(&ss, 0, sizeof(ss));

                if (QueryServiceStatus(service, &ss)) {
                    DWORD dwState = ss.dwCurrentState;

                    if (dwState == SERVICE_RUNNING)
                        printf("Failed to remove the service because the service is running\nStop the service and try again\n");
                    else if (dwState == SERVICE_STOP_PENDING)
                        printf("\
Failed to remove the service because the service is in stop pending state!\n\
Wait 30 seconds and try again.\n\
If this condition persist, reboot the machine and try again\n");
                    else
                        ret_value = TRUE;
                }

                CloseServiceHandle(service);
            }
        }

        CloseServiceHandle(scm);
    }

    return ret_value;
}
/* ------------------------------------------------------------------------
 -------------------------------------------------------------------------- */
BOOL NTService::IsService(LPCSTR ServiceName)
{
    BOOL ret_value = FALSE;
    SC_HANDLE service, scm;

    if ((scm = OpenSCManager(0, 0, SC_MANAGER_ENUMERATE_SERVICE))) {
        if ((service = OpenService(scm, ServiceName, SERVICE_QUERY_STATUS))) {
            ret_value = TRUE;
            CloseServiceHandle(service);
        }

        CloseServiceHandle(scm);
    }

    return ret_value;
}
/* ------------------------------------------------------------------------
 -------------------------------------------------------------------------- */
BOOL NTService::got_service_option(char **argv, char *service_option)
{
    char *option;

    for (option = argv[1]; *option; option++)
        if (!strcmp(option, service_option))
            return TRUE;

    return FALSE;
}
/* ------------------------------------------------------------------------
 -------------------------------------------------------------------------- */
BOOL NTService::is_super_user()
{
    HANDLE hAccessToken;
    UCHAR InfoBuffer[1024];
    PTOKEN_GROUPS ptgGroups = (PTOKEN_GROUPS)InfoBuffer;
    DWORD dwInfoBufferSize;
    PSID psidAdministrators;
    SID_IDENTIFIER_AUTHORITY siaNtAuthority = SECURITY_NT_AUTHORITY;
    UINT x;
    BOOL ret_value = FALSE;

    if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hAccessToken )) {
        if (GetLastError() != ERROR_NO_TOKEN)
            return FALSE;

        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hAccessToken))
            return FALSE;
    }

    ret_value = GetTokenInformation(hAccessToken, TokenGroups, InfoBuffer,
                                    1024, &dwInfoBufferSize);
    CloseHandle(hAccessToken);

    if (!ret_value )
        return FALSE;

    if (!AllocateAndInitializeSid(&siaNtAuthority, 2,
                                  SECURITY_BUILTIN_DOMAIN_RID,
                                  DOMAIN_ALIAS_RID_ADMINS,
                                  0, 0, 0, 0, 0, 0,
                                  &psidAdministrators))
        return FALSE;

    ret_value = FALSE;

    for (x = 0; x < ptgGroups->GroupCount; x++) {
        if ( EqualSid(psidAdministrators, ptgGroups->Groups[x].Sid) ) {
            ret_value = TRUE;
            break;
        }
    }

    FreeSid(psidAdministrators);
    return ret_value;
}
